// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package app

import (
	"appengine"
	"appengine/channel"
	"appengine/taskqueue"
	"bytes"
	"crypto/sha1"
	"fmt"
	"goray/job"
	"goray/std/yamlscene"
	"http"
	"io"
	"os"
	"url"

	_ "goray/std/all"
)

const bucket = "http://appengine-go-ray.commondatastorage.googleapis.com/"

func init() {
	http.HandleFunc("/task/render", renderTask)
	http.HandleFunc("/task/send", sendTask)
}

func renderTask(w http.ResponseWriter, r *http.Request) {
	var (
		c        = appengine.NewContext(r)
		scene    = r.FormValue("scene")
		clientid = r.FormValue("clientid")
	)

	// send is a helper closure to make sending messages more concise.
	send := func(msg string) {
		c.Infof("renderTask: %s", msg) // log the message to dashboard
		sendMessage(c, clientid, msg)
	}

	// Recover from panics.
	// If programmer error causes this task to crash,
	// we should notify the end user.
	defer func() {
		if e := recover(); e != nil {
			c.Criticalf("panic: %v", e)
			send("Error: render failed.")
		}
	}()

	send("Rendering...")

	// Set up the goray job.
	j := job.New("job", bytes.NewBufferString(scene), yamlscene.Params{
		"OutputFormat": job.FormatMap["png"],
	})

	// Render the image.
	var image bytes.Buffer
	if err := j.Render(&image); err != nil {
		send(fmt.Sprintf("Error: %v", err))
		c.Errorf("renderTask: %v", err)
		return
	}

	send("Render complete.")

	send("Uploading...")

	// Use a hash of the scene data as the image filename.
	h := sha1.New()
	io.WriteString(h, scene)
	imageURL := fmt.Sprintf("%s%x.png", bucket, h.Sum())

	if err := putImage(c, imageURL, &image); err != nil {
		send(fmt.Sprintf("Error: %v", err))
		c.Errorf("renderTask: putImage: %v", err)
		return
	}

	send("Upload complete.")

	// The front-end looks for a message beginning with "Image: " and
	// interprets it as the final image URL.
	send("Image: " + imageURL)
}

// putImage reads image data from r and uploads it to url using authentication
// data stored in the datastore.
func putImage(c appengine.Context, url string, r io.Reader) os.Error {
	rt := oauth2Transport(c)
	if rt == nil {
		return os.NewError("couldn't load OAuth2 credentials.")
	}

	req, err := http.NewRequest("PUT", url, r)
	if err != nil {
		return err
	}
	req.Header.Set("x-goog-acl", "public-read")

	resp, err := rt.RoundTrip(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return os.NewError("uploading image: " + resp.Status)
	}

	return nil
}

// sendMessage sends msg to the channel identified by clientid.
func sendMessage(c appengine.Context, clientid, msg string) {
	// FIXME: This is a work-around for broken Channel API on back-ends.
	// Really we should just be doing the channel.Send here,
	// but instead we must create a task to send the message
	// from the front-end.
	// http://code.google.com/p/googleappengine/issues/detail?id=5123

	// Create send task.
	t := taskqueue.NewPOSTTask("/task/send", url.Values{
		"id": {clientid}, "msg": {msg},
	})

	// Send the task to a normal app instance, as the renderTask
	// should be executed on the backend. By default, new tasks go
	// run on the instance that creates them.
	if !appengine.IsDevAppServer() {
		t.Header.Set("Host", appengine.DefaultVersionHostname(c))
	}

	// Add task to the queue.
	if _, err := taskqueue.Add(c, t, ""); err != nil {
		c.Errorf("renderTask: %v", err)
	}
}

// sendTask is a task that sends msg to a channel identified by id.
func sendTask(w http.ResponseWriter, r *http.Request) {
	var (
		c   = appengine.NewContext(r)
		id  = r.FormValue("id")
		msg = r.FormValue("msg")
	)
	if err := channel.Send(c, id, msg); err != nil {
		c.Errorf("sending to %q: %v", id, err)
	}
}
